#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import time

from common.cleaner import clear
from common.common import check_path_legal, execute_cmd
from common.const import ParamConstant, DBLogLevel, SubJobStatusEnum, CMDResult
from common.common_models import SubJobDetails, LogDetail
from common.enums.common_enums import DeployTypeEnum
from common.parse_parafile import get_env_variable
from common.security.anonym_utils.anonymity import Anonymity
from generaldb.saphana.comm.common_util import get_localhost_hostname
from generaldb.saphana.restore.saphana_restore_parent import SaphanaRestoreParent
from generaldb.saphana.restore.saphana_parse_restore_params import SaphanaParseRestoreParam
from generaldb.saphana.comm.saphana_const import SaphanaProgressPhase, SaphanaTaskLabel, SaphanaReportInterval, \
    SaphanaJsonConstant, SaphanaReportStatus, SaphanaErrorCode
from generaldb.saphana.comm.saphana_file_operate import log, FileOperate


class SaphanaRestoreSystemDb(SaphanaRestoreParent):
    def __init__(self, pid, job_id, subjob_id, parse_params_obj: SaphanaParseRestoreParam):
        super(SaphanaRestoreSystemDb, self).__init__(pid, job_id, subjob_id, parse_params_obj)
        self._error_code = 0
        self._error_param_list = []

    def wait_restore(self):
        # 系统数据库恢复 等待用户用其他工具恢复完成
        file_name = os.path.join(ParamConstant.PARAM_FILE_PATH, f"hana_systemdb_{self._job_id}")
        if not check_path_legal(file_name, ParamConstant.PARAM_FILE_PATH):
            log.error(f"Path({file_name}) not legal. {self._parse_param.get_log_common()}")
            return False
        while True:
            if os.path.exists(file_name):
                break
            time.sleep(SaphanaReportInterval.REPORT_PROGRESS_INTERVAL)
        output = FileOperate.read_json_file(file_name)
        if not output:
            log.error(f"Output is null. {self._parse_param.get_log_common()}")
            return False
        status = output.get(SaphanaJsonConstant.STATUS, "FAIL")
        if status != SaphanaReportStatus.SUCCESS:
            log.error(f"Recv status({status}) failed. {self._parse_param.get_log_common()}")
            return False
        log.info(f"Restore success. {self._parse_param.get_log_common()}")
        return True

    def get_restore_label_path(self):
        try:
            dir_instance = self._saphana_cluster.get_dir_instance()
        except Exception as exception_str:
            log.error(f"Get dir instance failed. {self._parse_param.get_log_common()}")
            return ""
        timestamp = self._parse_param.get_log_restore_timestamp()
        if timestamp:
            label_path = self.restore_log_copy(dir_instance)
        else:
            label_path = self.restore_data_copy(dir_instance)
        return label_path

    def progress_func(self):
        sub_job_details = SubJobDetails(taskId=self._job_id, subTaskId=self._subjob_id,
                                        progress=SaphanaProgressPhase.RUNNING,
                                        taskStatus=SubJobStatusEnum.RUNNING.value).dict(by_alias=True)
        return sub_job_details

    def analyze_restore_output(self, output):
        """
        分析恢复命令的输入回显，如果没有错误，返回True，反之返回False
        """
        error_msg_list = ["recoverSys failed", "error", "ERROR"]
        has_error = True if any(item in output for item in error_msg_list) else False
        # 如果没有明确恢复成功的，恢复失败
        result_msg = None
        for single_line in output.split('\n'):
            if "ending recoverSys" in single_line:
                result_msg = single_line
                break
        result_msg = result_msg[result_msg.find("rc"):-1]
        result_msg = result_msg[result_msg.find('=') + 1:result_msg.find('(')].strip()
        if has_error or result_msg != "0":
            return False
        return True

    def exec_restore_data_cmd(self, copy_path):
        restore_cmd = f"su - {self._sid}adm -c \"HDBSettings.sh recoverSys.py --command=" \
                      f"\\\"RECOVER DATA USING FILE ('{copy_path}/') " \
                      f"clear log\\\" --wait\""
        log.info(f"Begin to execute restore cmd: {restore_cmd}")
        ret, output, err = execute_cmd(restore_cmd)
        # 如果输出中有错误日志，恢复失败
        is_success = self.analyze_restore_output(output)
        if ret != CMDResult.SUCCESS or not is_success:
            log.error(f"Exec restore data cmd failed. out:{output}. {self._parse_param.get_log_common()}")
            self._error_code = SaphanaErrorCode.EXEC_BACKUP_RECOVER_CMD_FAIL
            self._error_param_list = ["recover", Anonymity.process(output)] if output else ["recover", "unknown"]
            return False
        return True

    def exec_restore_log_cmd(self, restore_time, cata_log_path, log_data_path, copy_data_path, backup_id):
        if not self._check_recover(copy_data_path, log_data_path, cata_log_path, restore_time):
            log.error(f"Check recover failed. {copy_data_path} {log_data_path} {cata_log_path} {restore_time}")
            return False
        ignore = " IGNORE DELTA DATA BACKUPS"*self._ignore_delta
        restore_cmd = f"su - {self._sid}adm -c \"HDBSettings.sh recoverSys.py --command=" \
                      f"\\\"RECOVER DATABASE UNTIL TIMESTAMP '{restore_time}' CLEAR LOG USING CATALOG PATH " \
                      f"('{cata_log_path}') USING LOG PATH ('{log_data_path}') USING DATA PATH ('{copy_data_path}') " \
                      f"USING BACKUP_ID {backup_id}{ignore}\\\" --wait\""
        log.info(restore_cmd)
        log.info(f"Begin to execute restore cmd: {restore_cmd}")
        ret, output, err = execute_cmd(restore_cmd)
        is_success = self.analyze_restore_output(output)
        if ret != CMDResult.SUCCESS or not is_success:
            log.error(f"Exec restore log cmd failed. out:{output}. {self._parse_param.get_log_common()}")
            self._error_code = SaphanaErrorCode.EXEC_BACKUP_RECOVER_CMD_FAIL
            self._error_param_list = ["recover", Anonymity.process(output)] if output else ["recover", "unknown"]
            return False
        return True

    def allow_restore(self):
        backup_master_hostname = self._parse_param.get_backup_master_hostname()
        if not backup_master_hostname:
            log.warning(f"Fail to get last backup master hostname")
            return False
        backup_obj_id = self._parse_param.get_backup_object_uuid()
        restore_obj_id = self._parse_param.get_restore_object_uuid()
        if backup_obj_id != restore_obj_id:
            # 如果恢复到新集群
            return True
        # 如果恢复到原集群，则恢复命令需要在备份的节点执行
        if self._parse_param.get_deploy_type() == DeployTypeEnum.SINGLE.value:
            return True
        local_hostname = get_localhost_hostname()
        if backup_master_hostname.upper() != local_hostname.upper():
            log.error(f"Host : {local_hostname} is not the backup host {backup_master_hostname}, can not run.")
            return False
        return True
